`
https://leetcode.cn/problems/super-ugly-number/
`

/**
 * @param {number} n
 * @param {number[]} primes
 * @return {number}
 */
var nthSuperUglyNumber = function (n, primes) {
  // 优先队列中装三元组 {product, prime, pi}
  // 其中 product 代表链表节点的值，prime 是计算下一个节点所需的质数因子，pi 代表链表上的指针
  const pq = new Heap((a, b) => a[0] - b[0])
  for (const prime of primes) {
    pq.push([1, prime, 1]);
  }

  // 可以理解为最终合并的有序链表（结果链表）
  const ugly = new Array(n + 1);
  // 可以理解为结果链表上的指针
  let p = 1;

  while (p <= n) {
    // 取三个链表的最小结点
    const pair = pq.pop()
    const product = pair[0];
    const prime = pair[1];
    const index = pair[2];

    // 避免结果链表出现重复元素
    if (product !== ugly[p - 1]) {
      // 接到结果链表上
      ugly[p] = product;
      p++;
    }

    // 生成下一个节点加入优先级队列
    pq.push([ugly[index] * prime, prime, index + 1]);
  }
  return ugly[n];
};

class Heap {
  constructor(compare) {
    this.heap = [];
    this.compare = compare;
  }
  size() {
    return this.heap.length;
  }
  isEmpty() {
    return this.heap.length === 0;
  }
  peek() {
    return this.heap[0] ?? undefined;
  }
  push(value) {
    this.heap.push(value);
    this.bubbleUp(this.heap.length - 1);
  }
  bubbleUp(index) {
    while (index > 0) {
      const parentIndex = Math.floor((index - 1) / 2);
      if (this.compare(this.heap[index], this.heap[parentIndex]) < 0) {
        [this.heap[index], this.heap[parentIndex]] = [this.heap[parentIndex], this.heap[index]];
        index = parentIndex;
      } else break;
    }
  }
  pop() {
    if (this.isEmpty()) return undefined;
    const top = this.heap[0];
    const last = this.heap.pop();
    if (!this.isEmpty()) {
      this.heap[0] = last;
      this.sinkDown(0);
    }
    return top;
  }
  sinkDown(index) {
    let current = index;
    const length = this.size();
    while (true) {
      const left = 2 * current + 1;
      const right = 2 * current + 2;
      let target = current;
      if (left < length && this.compare(this.heap[left], this.heap[target]) < 0)
        target = left;
      if (right < length && this.compare(this.heap[right], this.heap[target]) < 0)
        target = right;
      if (target !== current) {
        [this.heap[current], this.heap[target]] = [this.heap[target], this.heap[current]];
        current = target;
      } else break;
    }
  }
  update(oldValue, newValue) {
    const index = this.heap.indexOf(oldValue);
    if (index === -1) return false;
    this.heap[index] = newValue;
    this.bubbleUp(index);
    this.sinkDown(index);
    return true;
  }
  remove(value) {
    const index = this.heap.indexOf(value);
    if (index === -1) return false;
    const last = this.heap.pop();
    if (index === this.heap.length) return true;
    this.heap[index] = last;
    this.bubbleUp(index);
    this.sinkDown(index);
    return true;
  }
  contains(value) {
    return this.heap.includes(value);
  }
}